/* bs_system.c : bbs system -- bbs common system data
 * CopyRight (C) 2008  dgnbbs
 * drangon_zhou@21cn.com
 * 2008-10
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "bs_system.h"
#include "bs_account.h"

#include <stdio.h>
#include <string.h>
#include <sys/stat.h> /* umask() */

/************************/
static struct bs_system_obj_t
{
	os_cfg_t cfg;

	/* shm data */
	os_shm_t uidshm;
//	os_shm_t utmpshm;
//	struct bi_ucache_t * uidshm;
//	struct bi_utmpfile_t * utmpshm;
//	struct bi_bcache_t * brdshm;
//	struct bi_topcache_t * topshm;
	os_file_t utmplock;

} * s_system_obj = NULL;

static int load_shm_ucache()
{
	int i;
	os_file_t fd;
	struct bs_account_t acct;
	struct bs_ucache_t * uc = bs_shm_ucache();
	if( (fd = os_file_open( BBS_FILE_PASSWDS )) == OS_FILE_INVALID_HANDLE ) {
		return -1;
	}
	memset( uc, 0, sizeof(uc) );
	for( i = 0; i < BBS_MAXUSER; i++ ) {
		if( os_file_read( fd, sizeof(acct) * i, (char *)&acct, sizeof(acct) ) < 0 )
			break;
		strncpy( uc->userid[i], acct.userid, BBS_IDLEN );
		uc->userid[i][BBS_IDLEN] = '\0';
	}
	os_file_close( fd );
	uc->number = i;
	uc->uptime = time(NULL);
	return 0;
}

/****************************/
int bs_system_open( os_cfg_t cfg )
{
	const char * val;

	/* <1> env init */
	if( s_system_obj != NULL )
		return -1;

	/* NOTE : not do this, let start script do it
	val = os_cfg_get( cfg, "BBS_HOME" );
	if( chdir( val ) < 0 ) {
		PR_ERR( "chdir [%s] failed, errno %d", BBS_HOME, errno );
		return -1;
	}
	if( setgid( os_cfg_getl( cfg, "BBS_GID" ) ) < 0 
				|| setuid( os_vfg_getl( cfg, "BBS_UID" ) ) < 0 ) {

		PR_ERR( "set uid or gid failed, errno %d", errno );
		return -1;
	}
	umask( 0002 );
	*/

	/* <2> obj init */
	srand( time(NULL) * 2 + getpid() );
	s_system_obj = malloc( sizeof(*s_system_obj) );
	memset( s_system_obj, 0, sizeof(*s_system_obj) );
	s_system_obj->cfg = cfg;

	/* <2> shm init */
	s_system_obj->uidshm = os_shm_open( os_cfg_getl( cfg, "UCACHE_SHMKEY", -1 ), 
			sizeof(struct bs_ucache_t) );
	s_system_obj->utmplock = OS_FILE_INVALID_HANDLE;

	if( s_system_obj->uidshm == NULL ) {
		PR_ERR( "create shm failed" );
		bs_system_close();
		return -1;
	}

	return 0;
}

int bs_system_open_init( os_cfg_t cfg )
{
	if( bs_system_open( cfg ) < 0 ) {
		return -1;
	}

	load_shm_ucache();

	return 0;
}

int bs_system_close()
{
	if( s_system_obj == NULL ) {
		PR_ERR( "system obj is NULL" );
		return -1;
	}

	if( s_system_obj->utmplock != OS_FILE_INVALID_HANDLE ) {
		PR_WARN( "utmplock is still locked" );
		os_file_close( s_system_obj->utmplock );
	}

	if( s_system_obj->uidshm != NULL ) {
		os_shm_close( s_system_obj->uidshm );
	}

	memset( s_system_obj, 0, sizeof(*s_system_obj) );
	free( s_system_obj ), s_system_obj = NULL;
	return 0;
}

const os_cfg_t bs_system_getcfg()
{
	return s_system_obj->cfg;
}

struct bs_ucache_t * bs_shm_ucache()
{
	return (struct bs_ucache_t *)os_shm_address( s_system_obj->uidshm );
}

/*
///////////////////////////////////////////////////
////	utmp util
int bs_utmpshm_lock( struct bs_system_t * sys )
{
	ASSERT( sys != NULL && sys->utmpshm != NULL );

	if( sys->utmplock >= 0 ) {
		PR_ERR( "utmp already lock!" );
		return 0;
	}

	char buf[256];
	char hostname[256];
	gethostname( hostname, sizeof(hostname) );
	snprintf( buf, sizeof(buf), BBS_USHM_LOCK_NAME_BASE, hostname );

	sys->utmplock = os_filelock_lock( buf );
	if( sys->utmplock < 0 )
		return -1;

	return 0;
}

int bs_utmpshm_unlock( struct bs_system_t * sys )
{
	ASSERT( sys != NULL && sys->utmpshm != NULL );

	if( sys->utmplock < 0 ) {
		PR_ERR( "utmp not lock yet!" );
		return -1;
	}

	os_filelock_unlock( sys->utmplock );
	sys->utmplock = (os_filelock_t)(-1);

	return 0;
}


////////////////////////////////////////////////

int32_t bs_new_user( struct bs_system_t * sys, const char * userid, const char * passwd )
{
	ASSERT( sys != NULL && userid != NULL && userid[0] != '\0' && passwd != NULL && passwd[0] != '\0' );

	int32_t uid = -1;
	struct bi_userrec_t urec;
	int fd;

	if( (fd = open( BBS_FILE_PASSWDS, O_RDWR | O_CREAT, 0644 )) == -1 ) {
		PR_ERR( "open %s failed, errno %d", BBS_FILE_PASSWDS, errno );
		return -1;
	}

	if( flock( fd, LOCK_EX ) < 0 ) {
		PR_ERR( "lock fd failed, errno %d", errno );
		close( fd );
		return -1;
	}

	// find space
	int i;
	for( i = 0; i < BBS_MAXUSER; i++ ) {
		if( sys->uidshm->userid[i][0] != '\0' )
			continue;
		// FIXME : deal with file size less than BBS_MAXUSER, we need to append new record at file
		if( os_file_read( fd, i * sizeof(urec), &urec, sizeof(urec) ) != sizeof(urec) )
			break;
		if( urec.userid[0] != '\0' ) {
			PR_ERR( "record %d not match with shm!", i );
			continue;
		}
		uid = i;
		break;
	}
	if( uid == -1 ) {
		PR_ERR( "read file failed or no empty space, i %d, error %d", i, errno );
		flock( fd, LOCK_UN );
		close( fd );
		return -1;
	}

	memset( &urec, 0, sizeof(urec) );
	strncpy( urec.userid, userid, BBS_IDLEN );
	urec.userid[ BBS_IDLEN ] = '\0';
	urec.firstlogin = urec.lastlogin = time(NULL);
	urec.numlogins = 1;
	strncpy( urec.passwd, passwd, BBS_PASSLEN );
	strcpy( urec.termtype, "vt100" );
	urec.userdefine = (uint32_t)(-1);
	urec.userdefine &= ~(BBS_DEF_NOLOGINSEND);
	if( strcmp( urec.userid, "guest" ) == 0 ) {
		urec.userlevel = 0;
		urec.flags[0] = 0; // BBS_USER_CURSOR_FLAG;
		urec.userdefine &= ~(BBS_DEF_FRIENDCALL | BBS_DEF_ALLMSG | BBS_DEF_FRIENDMSG);
	}
	else {
		urec.userlevel = BBS_PERM_BASIC;
		urec.flags[0] = BBS_USER_PAGER_FLAG;
	}
	if( os_file_write( fd, uid * sizeof(urec), &urec, sizeof(urec) ) != sizeof(urec) ) {
		PR_ERR( "write file failed, uid %d, errno %d", uid, errno );
		flock( fd, LOCK_UN );
		close( fd );
		return -1;
	}
	strncpy( sys->uidshm->userid[uid], userid, BBS_IDLEN );
	sys->uidshm->userid[uid][BBS_IDLEN] = '\0';

	flock( fd, LOCK_UN );
	close( fd );

	return uid;
}

int bs_del_user( struct bs_system_t * sys, int32_t uid )
{
	ASSERT( sys != NULL && uid >= 0 && uid < BBS_MAXUSER );

	struct bi_userrec_t urec;
	int fd;

	if( (fd = open( BBS_FILE_PASSWDS, O_RDWR | O_CREAT, 0644 )) == -1 ) {
		PR_ERR( "open %s failed, errno %d", BBS_FILE_PASSWDS, errno );
		return -1;
	}

	if( flock( fd, LOCK_EX ) < 0 ) {
		PR_ERR( "lock fd failed, errno %d", errno );
		close( fd );
		return -1;
	}

	if( os_file_read( fd, uid * sizeof(urec), &urec, sizeof(urec) ) != sizeof(urec) ) {
		PR_ERR( "read fild failed, uid %d, errno %d", uid, errno );
		flock( fd, LOCK_UN );
		close( fd );
		return -1;
	}

	if( strcmp( urec.userid, sys->uidshm->userid[uid] ) != 0 ) {
		PR_ERR( "error, uid %d, urec [%s] -- shm [%s] not match", uid, urec.userid, sys->uidshm->userid[uid] );
		flock( fd, LOCK_UN );
		close( fd );
		return -1;
	}

	memset( &urec, 0, sizeof(urec) );
	if( os_file_write( fd, uid * sizeof(urec), &urec, sizeof(urec) ) != sizeof(urec) ) {
		PR_ERR( "write record failed, uid %d, errno %d", uid, errno );
		flock( fd, LOCK_UN );
		close( fd );
		return -1;
	}

	memset( sys->uidshm->userid[uid], 0, BBS_IDLEN );

	flock( fd, LOCK_UN );
	close( fd );

	return 0;
}

int bs_load_user_record( struct bs_system_t * sys, int32_t uid, struct bi_userrec_t * urec )
{
	ASSERT( sys != NULL && uid >= 0 && uid < BBS_MAXUSER && urec != NULL );

	int fd;
	if( (fd = open( BBS_FILE_PASSWDS, O_RDWR | O_CREAT, 0644 )) == -1 ) {
		PR_ERR( "open %s failed, errno %d", BBS_FILE_PASSWDS, errno );
		return -1;
	}

	if( os_file_read( fd, uid * sizeof(*urec), urec, sizeof(*urec) ) != sizeof(*urec) ) {
		PR_ERR( "read fild failed, uid %d, errno %d", uid, errno );
		close( fd );
		return -1;
	}

	if( strcmp( urec->userid, sys->uidshm->userid[uid] ) != 0 ) {
		PR_ERR( "error, uid %d, urec [%s] -- shm [%s] not match", uid, urec->userid, sys->uidshm->userid[uid] );
	}

	close( fd );

	return 0;
}

int bs_save_user_record( struct bs_system_t * sys, int32_t uid, const struct bi_userrec_t * urec )
{
	ASSERT( sys != NULL && uid >= 0 && uid < BBS_MAXUSER && urec != NULL );

	int fd;
	if( (fd = open( BBS_FILE_PASSWDS, O_RDWR | O_CREAT, 0644 )) == -1 ) {
		PR_ERR( "open %s failed, errno %d", BBS_FILE_PASSWDS, errno );
		return -1;
	}

	if( strcmp( urec->userid, sys->uidshm->userid[uid] ) != 0 ) {
		PR_ERR( "error, uid %d, urec [%s] -- shm [%s] not match", uid, urec->userid, sys->uidshm->userid[uid] );
	}

	if( os_file_write( fd, uid * sizeof(*urec), urec, sizeof(*urec) ) != sizeof(*urec) ) {
		PR_ERR( "read fild failed, uid %d, errno %d", uid, errno );
		close( fd );
		return -1;
	}

	close( fd );

	return 0;
}
*/

